﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Threading;
using TiaoTiaoCode.Common.ClassEntities;
using TiaoTiaoCode.Common.Enums;
using TiaoTiaoCode.Common.Extensions;
using TiaoTiaoCode.Common.Formatters;
using TiaoTiaoCode.Common.Helpers;
using TiaoTiaoCode.Modbuser.Interfaces;

namespace TiaoTiaoCode.Modbuser.Modbusers
{
    internal class RtuModbuser : IRtuModbuser, IDisposable
    {
        #region

        const int ProtocolHeadBytesLength = 8;

        #endregion

        #region 字段

        readonly AutoResetEvent _receiveLock;
        readonly TiaoTiaoIncrementCount _incrementCount;
        readonly SerialPort _serialPort;
        readonly TiaoTiaoByteTransFormatter _byteFormatter;

        /// <summary>
        /// 是否接收数据并处理
        /// </summary>
        byte _needReceive = 0;

        /// <summary>
        /// 接收数据
        /// </summary>
        byte[] _receiveBuffer;


        Action<byte[]> _finishReceive;

        #endregion

        #region 属性

        /// <summary>
        /// 串口名
        /// </summary>
        public string PortName { get; private set; }

        /// <summary>
        /// /波特率
        /// </summary>
        public uint Baud { get; private set; }

        /// <summary>
        /// 奇偶检验位
        /// </summary>
        public Parity Parity { get; private set; }

        /// <summary>
        /// 数据位
        /// </summary>
        public ushort DataBit { get; private set; }

        /// <summary>
        /// 停止位
        /// </summary>
        public StopBits StopBit { get; private set; }

        /// <summary>
        /// 站号
        /// </summary>
        public byte Station { get; private set; }

        /// <summary>
        /// 字节流顺序
        /// </summary>
        public ByteDataFormatType DataFormat { get; private set; }

        /// <summary>
        /// 首地址是否为0
        /// </summary>
        public bool AddressStartWithZero { get; private set; }

        /// <summary>
        /// 字符串是否反转
        /// </summary>
        public bool IsStringReverse { get; private set; }

        /// <summary>
        /// 检查消息是否一致
        /// </summary>
        public bool CheckMessageId { get; private set; }

        /// <summary>
        /// short ushort 是否反转
        /// </summary>
        public bool IsInteger16Reverse { get; private set; }

        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        public RtuModbuser()
        {
            PortName = "COM1";
            Baud = 9600;
            Parity = Parity.None;
            DataBit = 8;
            StopBit = StopBits.One;

            Station = 1;
            DataFormat = ByteDataFormatType.CDAB;
            IsInteger16Reverse = true;

            _receiveLock = new AutoResetEvent(false);
            _incrementCount = new TiaoTiaoIncrementCount(int.MaxValue);
            _byteFormatter = new TiaoTiaoByteTransFormatter { IsStringReverseByteWord = false, DataFormat = ByteDataFormatType.CDAB };

            _serialPort = new SerialPort();

            _serialPort.DataReceived += SerialOnDataReceived;
        }

        #region ITcpModbuser

        /// <summary>
        /// 设置
        /// </summary>
        /// <param name="portName">串口名</param>
        /// <param name="baud">波特率</param>
        /// <param name="parity">检验位</param>
        /// <param name="dataBit">数据位</param>
        /// <param name="stopBits">停止位</param>
        /// <returns></returns>
        public IRtuModbuser SetConfig(string portName = "COM1", uint baud = 9600, Parity parity = Parity.None, ushort dataBit = 8, StopBits stopBits = StopBits.One)
        {
            if (_serialPort == null) throw new Exception("未找到串口对象");

            _serialPort.PortName = portName;
            _serialPort.BaudRate = 9600;
            _serialPort.Parity = parity;
            _serialPort.DataBits = dataBit;
            _serialPort.StopBits = stopBits;

            return this;
        }

        /// <summary>
        /// 设置站号
        /// </summary>
        /// <param name="station"></param>
        /// <returns></returns>
        public IRtuModbuser SetStation(byte station)
        {
            Station = station;

            return this;
        }

        /// <summary>
        /// 设置字节流顺序
        /// </summary>
        /// <param name="dataFormat"></param>
        /// <returns></returns>
        public IRtuModbuser SetByteDataFormat(ByteDataFormatType dataFormat)
        {
            DataFormat = dataFormat;

            return this;
        }

        /// <summary>
        /// 设置是否首地址为0
        /// </summary>
        /// <param name="addressStartWithZero"></param>
        /// <returns></returns>
        public IRtuModbuser SetAddressStartWithZero(bool addressStartWithZero)
        {
            AddressStartWithZero = addressStartWithZero;

            return this;
        }

        /// <summary>
        /// 设置字符串是否反转
        /// </summary>
        /// <param name="isStringReverse"></param>
        /// <returns></returns>
        public IRtuModbuser SetIsStringReverse(bool isStringReverse)
        {
            IsStringReverse = isStringReverse;

            return this;
        }

        /// <summary>
        /// 设置是否检查消息号一致
        /// </summary>
        /// <param name="checkMessageId"></param>
        /// <returns></returns>
        public IRtuModbuser SetCheckMessageId(bool checkMessageId)
        {
            CheckMessageId = checkMessageId;

            return this;
        }

        /// <summary>
        /// 读取连续的数据内容
        /// </summary>
        /// <typeparam name="T">限定只能是基础数据类型</typeparam>
        /// <param name="startAddress">开始读取的地址位</param>
        /// <param name="readLength">读取长度</param>
        /// <returns></returns>
        public T[] Read<T>(string startAddress, ushort readLength)
        {
            ConntectServer();

            var type = typeof(T);
            var typeCode = Type.GetTypeCode(type);
            var result = Read<T>(startAddress, readLength, typeCode);

            DisconnectServer();

            return result;
        }

        #endregion

        #region IModbuser

        /// <summary>
        /// 连接
        /// </summary>
        /// <returns></returns>
        public bool ConntectServer()
        {
            if (_serialPort == null)
                throw new Exception("未创建客户端，不能连接");

            _serialPort.Open();

            return _serialPort.IsOpen;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisconnectServer()
        {
            _serialPort.Close();
        }

        /// <summary>
        /// 读
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="addresses"></param>
        public T[] Read<T>(params string[] addresses)
        {
            ConntectServer();

            var type = typeof(T);
            var typeCode = Type.GetTypeCode(type);

            var result = new List<T>();

            foreach (var address in addresses)
            {
                var tmp = Read<T>(address, 1, typeCode);

                result.AddRange(tmp);
            }

            DisconnectServer();

            return result.ToArray();
        }

        /// <summary>
        /// 写
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="address"></param>
        /// <param name="value"></param>
        public bool Write<T>(string address, T value)
        {
            ConntectServer();

            var type = typeof(T);
            var typeCode = Type.GetTypeCode(type);

            switch (typeCode)
            {
                //case TypeCode.Boolean:
                //    {
                //        var bytes = ((bool)(object)value!)
                //                    ? new byte[] {0xff,0x00}
                //                    : new byte[] { 0x00, 0x00 };

                //        Write(address, bytes);

                //        return true;
                //    }
                case TypeCode.Int16:
                    {
                        var bytes = BitConverter.GetBytes((short)(object)value!);

                        Write(address, bytes, ModbusCommand.WriteOneRegister);

                        break;
                    }
                case TypeCode.Int32:
                    {
                        var bytes = BitConverter.GetBytes((int)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat4(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.Int64:
                    {
                        var bytes = BitConverter.GetBytes((long)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat8(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.Single:
                    {
                        var bytes = BitConverter.GetBytes((float)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat4(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.UInt16:
                    {
                        var bytes = BitConverter.GetBytes((ushort)(object)value!);

                        Write(address, bytes, ModbusCommand.WriteOneRegister);

                        break;
                    }
                case TypeCode.UInt32:
                    {
                        var bytes = BitConverter.GetBytes((uint)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat4(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.UInt64:
                    {
                        var bytes = BitConverter.GetBytes((ulong)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat8(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.Double:
                    {
                        var bytes = BitConverter.GetBytes((double)(object)value!);
                        bytes = _byteFormatter.ByteTransDataFormat8(bytes);

                        Write(address, bytes, ModbusCommand.WriteRegister);

                        break;
                    }
                case TypeCode.String:
                default:
                    throw new Exception("未识别的读取类型");
            }

            DisconnectServer();

            return true;
        }

        #endregion

        #region IDispose

        public void Dispose()
        {
            if (_serialPort != null)
            {
                _serialPort.DataReceived -= SerialOnDataReceived;
            }

            _serialPort?.Close();
            _serialPort?.Dispose();
        }

        #endregion

        #region 私有

        void SerialOnDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (sender is not SerialPort sp) return;

            byte[] buffer = new byte[1024];
            MemoryStream ms = new MemoryStream();
            DateTime start = DateTime.Now;                           // 开始时间，用于确认是否超时的信息
            int receiveEmptyCount = 0;                                      // 当前接收空数据的次数统计
            int cycleCount = 0;                                      // 当前接收循环的计数

            while (true)
            {
                if (_serialPort.BytesToRead < 1)
                {
                    if (cycleCount == 1) continue;                      // 第一次接收没有数据的话，立即进行再次接收
                    if ((DateTime.Now - start).TotalMilliseconds > 5000)
                    {
                        ms.Dispose();
                        _receiveBuffer = null!;
                        break;
                    }
                    else if (ms.Length >= 1)
                    {
                        receiveEmptyCount++;
                        if (receiveEmptyCount >= 1) break;
                        continue;
                    }
                    else
                    {
                        break;
                    }
                }
                else
                {
                    receiveEmptyCount = 0;
                }

                // 继续接收数据
                int sp_receive = sp.Read(buffer, 0, buffer.Length);
                if (sp_receive > 0) ms.Write(buffer, 0, sp_receive);

                if (CheckReceiveDataComplete(ms.ToArray())) break;
            }

            //var buffer = ReceiveFromSocket();
            buffer = ms.ToArray().TiaoTiaoArrayRemoveBegin(6);
            //buffer = ExtractActualData(buffer);            

            _finishReceive?.Invoke(buffer);

            _receiveLock.Set();
        }

        /// <summary>
        /// 统一的读取入口
        /// </summary>
        /// <param name="address"></param>
        /// <param name="length"></param>
        /// <param name="typeCode"></param>
        /// <exception cref="Exception"></exception>
        T[] Read<T>(string address, ushort length, TypeCode typeCode)
        {
            byte[] recBytes;

            switch (typeCode)
            {
                //case TypeCode.Boolean:
                case TypeCode.Int16:
                    {
                        recBytes = Read(address, GetWordLength(length, 1));

                        var tmp = TransToInt16(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Int32:
                    {
                        recBytes = Read(address, GetWordLength(length, 2));

                        var tmp = TransInt32(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Int64:
                    {
                        recBytes = Read(address, GetWordLength(length, 4));

                        var tmp = TransInt64(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Single:
                    {
                        recBytes = Read(address, GetWordLength(length, 2));

                        var tmp = TransSingle(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.UInt16:
                    {
                        recBytes = Read(address, GetWordLength(length, 1));

                        var tmp = TransUInt16(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.UInt32:
                    {
                        recBytes = Read(address, GetWordLength(length, 2));

                        var tmp = TransUInt32(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.UInt64:
                    {
                        recBytes = Read(address, GetWordLength(length, 4));

                        var tmp = TransUInt64(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.Double:
                    {
                        recBytes = Read(address, GetWordLength(length, 4));

                        var tmp = TransDouble(recBytes, 0, length);
                        return (T[])(object)tmp;
                    }
                case TypeCode.String:
                default:
                    throw new Exception("未识别的读取类型");
            }
        }

        /// <summary>
        /// 拼指令读取
        /// </summary>
        /// <param name="address"></param>
        /// <param name="realLength"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        byte[] Read(string address, ushort realLength)
        {
            var cmds = CommandBuilder.BuildReadCommand(address, realLength, Station, AddressStartWithZero, ModbusCommand.ReadRegister);

            if (cmds.Length <= 0) throw new Exception("未能生成读取指令");

            var rec = new List<byte>();
            _finishReceive = new Action<byte[]>(t =>
            {
                rec.AddRange(t);
            });

            foreach (var cmd in cmds)
            {
                var send = PackCommandToRtu(cmd);

                _serialPort.Write(send, 0, send.Length);

                var b = _receiveLock.WaitOne(5000);
                if (!b)
                {
                    _finishReceive = null!;
                    throw new Exception("读取超时");
                }
            }

            _finishReceive = null!;

            return rec.ToArray();
        }

        void Write(string address, byte[] value, byte defaultFunc)
        {
            var cmd = CommandBuilder.BuildWriteCommand(address, value, Station, AddressStartWithZero, defaultFunc);
            cmd = PackCommandToRtu(cmd);
            _serialPort.Write(cmd, 0, cmd.Length);
        }

        /// <summary>
        /// 获取实际要读取的长度
        /// </summary>
        /// <param name="length"></param>
        /// <param name="dataTypeLength"></param>
        /// <returns></returns>
        ushort GetWordLength(int length, int dataTypeLength)
        {
            return (ushort)(length * dataTypeLength);
        }

        #endregion

        byte[] PackCommandToRtu(byte[] modbus) => TiaoTiaoCrc16.CRC16(modbus);

        #region 接收数据验证

        int GetContentLengthByHeadBytes(byte[] headBytes)
        {
            if (headBytes?.Length >= ProtocolHeadBytesLength)
            {
                int length = headBytes[4] * 256 + headBytes[5];
                if (length == 0)
                {
                    byte[] buffer = new byte[ProtocolHeadBytesLength - 1];
                    for (int i = 0; i < buffer.Length; i++)
                    {
                        buffer[i] = headBytes[i + 1];
                    }
                    headBytes = buffer;
                    return headBytes[5] * 256 + headBytes[6] - 1;
                }
                else
                {
                    return Math.Min(length - 2, 300);
                }
            }
            else
                return 0;
        }

        /// <summary>
        /// 提取真实数据
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        byte[] ExtractActualData(byte[] response)
        {
            if (response[1] >= 0x80) throw new Exception(GetDescriptionByErrorCode(response[2]));

            if (response.Length > 3) return response.TiaoTiaoArrayRemoveBegin(3);

            return new byte[0];
        }

        byte[] ExtraRtuResponseContent(byte[] send, byte[] response, bool crcCheck = true)
        {
            // 长度校验
            if (response.Length < 5) throw new Exception("接收数据包长度不足");

            // 检查crc
            if (crcCheck && !TiaoTiaoCrc16.CheckCRC16(response)) throw new Exception("CRC校验失败");

            // 站号检查
            if (send[0] != response[0]) throw new Exception("站号不一致");

            // 发生了错误
            if ((send[1] + 0x80) == response[1]) throw new Exception(GetDescriptionByErrorCode(response[0]));

            if (send[1] != response[1]) throw new Exception("回复包指令不一致");

            // 移除CRC校验，返回真实数据
            return ExtractActualData(response.TiaoTiaoArrayRemoveLast(2));
        }

        bool CheckReceiveDataComplete(byte[] response)
        {
            if (response.Length > 2)
            {
                if (response[1] == ModbusCommand.WriteOneRegister ||
                    response[1] == ModbusCommand.WriteRegister ||
                    response[1] == ModbusCommand.WriteCoil ||
                    response[1] == ModbusCommand.WriteOneCoil)
                    return response.Length >= (6 + 2);
                else if (
                    response[1] == ModbusCommand.ReadCoil ||
                    response[1] == ModbusCommand.ReadDiscrete ||
                    response[1] == ModbusCommand.ReadRegister ||
                    response[1] == ModbusCommand.ReadInputRegister)
                    return response.Length >= (response[2] + 3 + 2);
                else if (response[1] == ModbusCommand.WriteMaskRegister)
                    return response.Length >= (8 + 2);
                else if (response[1] > 0x80)
                    return response.Length >= 5;
            }
            return false;
        }
        #endregion

        #region Error

        /// <summary>
        /// 不支持该功能码
        /// </summary>
        const byte FunctionCodeNotSupport = 0x01;
        /// <summary>
        /// 该地址越界
        /// </summary>
        const byte FunctionCodeOverBound = 0x02;
        /// <summary>
        /// 读取长度超过最大值
        /// </summary>
        const byte FunctionCodeQuantityOver = 0x03;
        /// <summary>
        /// 读写异常
        /// </summary>
        const byte FunctionCodeReadWriteException = 0x04;

        string ModbusTcpFunctionCodeNotSupport => "不支持的功能码";
        string ModbusTcpFunctionCodeOverBound => "读取的数据越界";
        string ModbusTcpFunctionCodeQuantityOver => "读取长度超过最大值";
        string ModbusTcpFunctionCodeReadWriteException => "读写异常";
        string UnknownError => "未知错误";

        string GetDescriptionByErrorCode(byte code)
        {
            switch (code)
            {
                case FunctionCodeNotSupport: return ModbusTcpFunctionCodeNotSupport;
                case FunctionCodeOverBound: return ModbusTcpFunctionCodeOverBound;
                case FunctionCodeQuantityOver: return ModbusTcpFunctionCodeQuantityOver;
                case FunctionCodeReadWriteException: return ModbusTcpFunctionCodeReadWriteException;
                default: return UnknownError;
            }
        }

        #endregion

        #region 数据转换

        short[] TransToInt16(byte[] buffer, int index, int length)
        {
            short[] tmp = new short[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransInt16(buffer, index + 2 * i);
            }
            return tmp;
        }

        short TransInt16(byte[] buffer, int index)
        {
            if (IsInteger16Reverse)
            {
                byte[] tmp = new byte[2];
                tmp[0] = buffer[index + 1];
                tmp[1] = buffer[index + 0];
                return BitConverter.ToInt16(tmp, 0);
            }
            else
                return BitConverter.ToInt16(buffer, index);
        }

        int[] TransInt32(byte[] buffer, int index, int length)
        {
            int[] tmp = new int[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransInt32(buffer, index + 4 * i);
            }
            return tmp;
        }

        int TransInt32(byte[] buffer, int index) => BitConverter.ToInt32(_byteFormatter.ByteTransDataFormat4(buffer, index), 0);

        long[] TransInt64(byte[] buffer, int index, int length)
        {
            long[] tmp = new long[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransInt64(buffer, index + 8 * i);
            }
            return tmp;
        }

        long TransInt64(byte[] buffer, int index) => BitConverter.ToInt64(_byteFormatter.ByteTransDataFormat8(buffer, index), 0);

        float[] TransSingle(byte[] buffer, int index, int length)
        {
            float[] tmp = new float[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransSingle(buffer, index + 4 * i);
            }
            return tmp;
        }

        float TransSingle(byte[] buffer, int index) => BitConverter.ToSingle(_byteFormatter.ByteTransDataFormat4(buffer, index), 0);

        ushort[] TransUInt16(byte[] buffer, int index, int length)
        {
            ushort[] tmp = new ushort[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransUInt16(buffer, index + 2 * i);
            }
            return tmp;
        }

        ushort TransUInt16(byte[] buffer, int index) => BitConverter.ToUInt16(buffer, index);

        uint[] TransUInt32(byte[] buffer, int index, int length)
        {
            uint[] tmp = new uint[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransUInt32(buffer, index + 4 * i);
            }
            return tmp;
        }

        uint TransUInt32(byte[] buffer, int index) => BitConverter.ToUInt32(_byteFormatter.ByteTransDataFormat4(buffer, index), 0);

        ulong[] TransUInt64(byte[] buffer, int index, int length)
        {
            ulong[] tmp = new ulong[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransUInt64(buffer, index + 8 * i);
            }
            return tmp;
        }

        ulong TransUInt64(byte[] buffer, int index) => BitConverter.ToUInt64(_byteFormatter.ByteTransDataFormat8(buffer, index), 0);

        double[] TransDouble(byte[] buffer, int index, int length)
        {
            double[] tmp = new double[length];
            for (int i = 0; i < length; i++)
            {
                tmp[i] = TransDouble(buffer, index + 8 * i);
            }
            return tmp;
        }

        double TransDouble(byte[] buffer, int index) => BitConverter.ToDouble(_byteFormatter.ByteTransDataFormat8(buffer, index), 0);

        #endregion
    }
}
